// boolean, Number(int, float), string  ,  
// not included ==> (null, undefined, void) 
// not included Array, Tuple, Object 
// not included Custom-Class

import { IStorage } from "./i-storage"
import { Storage } from "./storage"
import {NotImplementedError } from "../error"


class ConvertTypeError extends  Error {
  from: string;
  to: string;
  constructor(from: string, to: string, message: string="") {
    super(`${from} convert to ${to} error: ${message}`);
    this.name = `convert from:${from} to ${to} error`;
    this.from = from;
    this.to = to;
  }
}


interface StringConvertable<T> {
  equals(left: T|undefined, right: T|undefined): boolean;
  unequals(left: T|undefined, right: T|undefined): boolean;

  parse(s:string):T;
  build(v: T): string ;
}

abstract class AbstractWithString<T> implements StringConvertable<T> {
  unequals(left: T|undefined, right: T|undefined): boolean{
    return !this.equals(left, right);
  }
  equals(left: T|undefined, right: T|undefined): boolean {
    return left === right;
  }
  build(v: T): string  {
    return v.toString();
  }
  parse(s:string): T {
    throw new NotImplementedError("function:[parse] not implements");
  }
}

class BooleanWithString extends AbstractWithString<boolean> {
  parse(s: string): boolean {
    if(s === "true") return true;
    else if( s === "false") return false;
    else { 
      throw new ConvertTypeError("string", "boolean");
    }
  }
}

/* TODO:
class StringConvertor<T> implements StringConvertable<T>{
  type: string;
  cvt: StringConvertor<T>;
  constructor() {
    this.type = type(T) ;
    switch(this.type){
      case "boolean":
        this.cvt = new BooleanWithString();
      case "number":
        //TODO:
        this.cvt = new NumberWithString();
      default:
        this.cvt = new BooleanWithString();
        break;
    }
  }

}*/
//new StringConvertor<boolean>




/* 
 *  new & call load =>  isSynced:true;  
 *                      value => undefined -> KeyNotExist
 *                            => else
 *
 *  get ->  return value 
 *
 *  load -> loadString():undefined => value = undefined
 *          loadString():else => parse it => value = new-value
 *          isSynced = true;
 *
 *  delete -> isSynced:true & value:undefined => do nothing;
 *            else =>  delete it & isSynced = true & value = nothing
 *
 *  set ->  if unequals(value, this.value) => set it && this._isSynced=false
 *
 *  save -> isSynced:true => do-nothing
 *          isSynced:false => if value:undefined => delete it 
 *                            if value:else => convert & save & isSynced=true;
 *
 */
export class VariableStorage {
  static loadFromDefault(key:string): VariableStorage {
    return VariableStorage.loadFrom(key, Storage.default());
  }
  static loadFrom(key:string, storage: IStorage): VariableStorage {
    return new VariableStorage(key, storage);
  }

  readonly _convertor: BooleanWithString;
  storage: IStorage;
  readonly key: string;
  value: boolean;
  _isSynced: boolean;

  constructor(key: string, storage: IStorage) {
    this._convertor = new BooleanWithString();
    this.storage = storage;
    this.key = key;

    this.load();
  }

  
  isSynced(): boolean{ 
    return this._isSynced;
  }

  
  load(): boolean | undefined{
    const ret = this.storage.loadString(this.key);
    if( ret !== undefined ){ 
      this.value = this._convertor.parse(ret);
    }
    else{  // not exists in storage
      // ret === undefined
      this.value = undefined;
    }
    
    this._isSynced = true;
    return this.value;
  }

  
  // undefined => means KeyNotExist
  get(): boolean | undefined {
    return this.value;
  }

  //if value diff this.value to change it && set-flag 
  set(value: boolean | undefined): boolean {
    if(this._convertor.unequals(value, this.value) ){
      this.value = value;
      this._isSynced = false;
      return true;
    }
    return false;
  }

  //true -> means write-synced
  save(): boolean {
    if(!this._isSynced) {
      if(this.value !== undefined) {
        const s = this._convertor.build(this.value);
        const ret = this.storage.saveString(this.key, s);
        if(ret){
          this._isSynced = true;
        }
        return ret;
      }
      else{ //isSynced == false & value == undefined
        this.storage.delete(this.key);
        this._isSynced = true;
        return true
      }
    }
    return true;
  }

  delete(): void {
    if(this._isSynced && this.value == undefined){
      //key not exists in here already
    }
    else{
      this.storage.delete(this.key)
      this.value = undefined;
      this._isSynced = true;
    }
  }
}


